
This tutorial uses data published by NASA:
Hat tip to https://fosstodon.org/@65dBnoise/108251277108722231 for providing the pointers
Known issues:
import numpy as np
import pandas as pd
import geopandas as gpd
import movingpandas as mpd
import shapely as shp
import hvplot.pandas
import matplotlib.pyplot as plt
from geopandas import GeoDataFrame, read_file
from shapely.geometry import Point, LineString, Polygon
from datetime import datetime, timedelta
from holoviews import opts, dim
from os.path import exists
from urllib.request import urlretrieve
import warnings
warnings.filterwarnings('ignore')
plot_defaults = {'linewidth':5, 'capstyle':'round', 'figsize':(9,3), 'legend':True}
opts.defaults(opts.Overlay(active_tools=['wheel_zoom'], frame_width=500, frame_height=400))
hvplot_defaults = {'tiles':None, 'cmap':'Viridis', 'colorbar':True}
mpd.show_versions()
MovingPandas 0.10.rc1 SYSTEM INFO ----------- python : 3.9.13 | packaged by conda-forge | (main, May 27 2022, 16:50:36) [MSC v.1929 64 bit (AMD64)] executable : H:\miniconda3\envs\mpd-ex\python.exe machine : Windows-10-10.0.19043-SP0 GEOS, GDAL, PROJ INFO --------------------- GEOS : None GEOS lib : None GDAL : 3.5.0 GDAL data dir: None PROJ : 9.0.0 PROJ data dir: H:\miniconda3\pkgs\proj-9.0.0-h1cfcee9_1\Library\share\proj PYTHON DEPENDENCIES ------------------- geopandas : 0.10.2 pandas : 1.4.2 fiona : 1.8.21 numpy : 1.22.4 shapely : 1.8.2 rtree : 1.0.0 pyproj : 3.3.1 matplotlib : 3.5.2 mapclassify: 2.4.3 geopy : 2.2.0 holoviews : 1.14.9 hvplot : 0.8.0 geoviews : 1.9.5 stonesoup : 0.1b9
"The car-sized Perseverance and its little helicopter buddy Ingenuity landed together inside Mars' Jezero Crater on Feb. 18." https://www.space.com/perseverance-rover-100-mars-days (by Mike Wall published June 02, 2021)
"One sol lasts about 24 hours and 40 minutes, slightly longer than an Earth day." https://www.space.com/perseverance-rover-100-mars-days
def to_timestamp(row):
start_time = datetime(2021,2,18,0,0,0) # sol 0
try:
sol = row['sol'] # rover
except KeyError:
sol = row['Sol'] # heli
td = timedelta(hours=24*sol, minutes=40*sol)
return start_time + td
def get_df_from_url(url):
file = url.split('/')[-1]
if not exists(file):
urlretrieve(url, file)
gdf = read_file(file)
gdf['time'] = gdf.apply(to_timestamp, axis=1)
gdf.set_index('time', inplace=True)
return gdf
m20_waypoints_json = "https://mars.nasa.gov/mmgis-maps/M20/Layers/json/M20_waypoints.json"
heli_waypoints_json = "https://mars.nasa.gov/mmgis-maps/M20/Layers/json/m20_heli_waypoints.json"
m20_df = get_df_from_url(m20_waypoints_json)
heli_df = get_df_from_url(heli_waypoints_json)
print(f'M20 records: {len(m20_df)}')
print(f'Heli records: {len(heli_df)}')
M20 records: 156 Heli records: 29
m20_df.describe()
| site | drive | sol | SCLK_START | SCLK_END | easting | northing | elev_geoid | elev_radii | radius | ... | roll | pitch | yaw | yaw_rad | tilt | dist_m | dist_total | dist_km | dist_mi | ORIG_FID | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| count | 156.000000 | 156.000000 | 156.000000 | 1.560000e+02 | 1.560000e+02 | 1.560000e+02 | 1.560000e+02 | 156.000000 | 156.000000 | 1.560000e+02 | ... | 156.000000 | 156.000000 | 156.000000 | 156.000000 | 156.000000 | 156.000000 | 156.000000 | 156.000000 | 156.000000 | 156.000000 |
| mean | 10.705128 | 1635.076923 | 261.012821 | 6.858288e+08 | 6.858989e+08 | 4.353919e+06 | 1.093211e+06 | -2565.319896 | -4248.434796 | 3.391942e+06 | ... | 0.078588 | 0.877516 | -29.754424 | -0.519312 | 3.247422 | 73.370897 | 3369.154237 | 3.369103 | 2.093141 | 83.500000 |
| std | 7.343391 | 1757.848735 | 141.790576 | 5.665063e+07 | 5.664626e+07 | 9.786968e+02 | 6.396507e+02 | 14.163160 | 13.179175 | 1.317897e+01 | ... | 3.123551 | 3.221801 | 106.107954 | 1.851936 | 3.148950 | 84.848770 | 3668.565027 | 3.668456 | 2.279536 | 45.177428 |
| min | 3.000000 | 0.000000 | 13.000000 | 0.000000e+00 | 0.000000e+00 | 4.351710e+06 | 1.092270e+06 | -2585.869629 | -4266.522461 | 3.391923e+06 | ... | -12.377935 | -7.797700 | -179.475000 | -3.132400 | -0.043088 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 6.000000 |
| 25% | 4.000000 | 410.000000 | 126.750000 | 6.781907e+08 | 6.781920e+08 | 4.353956e+06 | 1.092629e+06 | -2574.016500 | -4255.397319 | 3.391935e+06 | ... | -1.075334 | -0.520792 | -126.751645 | -2.212250 | 1.087888 | 8.878500 | 302.875000 | 0.305000 | 0.187500 | 44.750000 |
| 50% | 9.000000 | 1304.000000 | 330.500000 | 6.962873e+08 | 6.962881e+08 | 4.354411e+06 | 1.093255e+06 | -2569.165000 | -4252.637510 | 3.391937e+06 | ... | -0.120000 | 0.531261 | -58.070700 | -1.013550 | 1.995700 | 36.329000 | 2117.274000 | 2.120000 | 1.315000 | 83.500000 |
| 75% | 17.250000 | 2247.000000 | 395.250000 | 7.020252e+08 | 7.020356e+08 | 4.354547e+06 | 1.093694e+06 | -2558.318000 | -4245.486393 | 3.391945e+06 | ... | 1.179550 | 2.214195 | 45.275105 | 0.790200 | 4.457050 | 111.595500 | 5586.892750 | 5.587500 | 3.475000 | 122.250000 |
| max | 24.000000 | 9436.000000 | 437.000000 | 7.057373e+08 | 7.057424e+08 | 4.355183e+06 | 1.094703e+06 | -2520.911000 | -4204.339000 | 3.391986e+06 | ... | 13.177640 | 19.349400 | 179.893300 | 3.139700 | 19.833200 | 313.832000 | 11445.860000 | 11.450000 | 7.110000 | 161.000000 |
8 rows × 22 columns
m20_df.hvplot(title="M20 & heli waypoints", hover_cols=['sol'], **hvplot_defaults) * heli_df.hvplot()
m20_traj = mpd.Trajectory(m20_df, 'm20')
heli_traj = mpd.Trajectory(heli_df, 'heli')
traj_plot = m20_traj.hvplot(title="M20 & heli trajectories", line_width=3, **hvplot_defaults) * heli_traj.hvplot(line_width=3, **hvplot_defaults)
traj_plot
m20_traj.hvplot(title="Rover speed (only suitable for relative comparison)",
c='speed', line_width=7, **hvplot_defaults)
m20_detector = mpd.TrajectoryStopDetector(m20_traj)
stop_points = m20_detector.get_stop_points(min_duration=timedelta(seconds=60), max_diameter=100)
stop_points['duration_days'] = stop_points['duration_s']/(60*60*24)
stop_points.head()
| geometry | start_time | end_time | traj_id | duration_s | duration_days | |
|---|---|---|---|---|---|---|
| stop_id | ||||||
| m20_2021-03-03 08:40:00 | POINT Z (77.45089 18.44463 -2569.91000) | 2021-03-03 08:40:00 | 2021-03-05 10:00:00 | m20 | 177600.0 | 2.055556 |
| m20_2021-03-10 13:20:00 | POINT Z (77.45186 18.44515 -2568.93000) | 2021-03-10 13:20:00 | 2021-03-22 21:20:00 | m20 | 1065600.0 | 12.333333 |
| m20_2021-03-24 22:40:00 | POINT Z (77.45102 18.44486 -2569.99000) | 2021-03-24 22:40:00 | 2021-04-08 08:00:00 | m20 | 1243200.0 | 14.388889 |
| m20_2021-04-12 10:40:00 | POINT Z (77.45214 18.44475 -2569.05000) | 2021-04-12 10:40:00 | 2021-05-17 09:20:00 | m20 | 3019200.0 | 34.944444 |
| m20_2021-05-31 18:40:00 | POINT Z (77.45216 18.44376 -2569.19700) | 2021-05-31 18:40:00 | 2021-06-02 20:00:00 | m20 | 177600.0 | 2.055556 |
heli_detector = mpd.TrajectoryStopDetector(heli_traj)
heli_stop_points = heli_detector.get_stop_points(min_duration=timedelta(seconds=60), max_diameter=100)
heli_stop_points['duration_days'] = heli_stop_points['duration_s']/(60*60*24)
heli_stop_points.head()
| geometry | start_time | end_time | traj_id | duration_s | duration_days | |
|---|---|---|---|---|---|---|
| stop_id | ||||||
| heli_2021-04-03 04:40:00 | POINT Z (77.45102 18.44486 -2570.00000) | 2021-04-03 04:40:00 | 2021-04-29 22:00:00 | heli | 2308800.0 | 26.722222 |
| heli_2021-08-04 12:40:00 | POINT Z (77.43878 18.43266 -2569.86000) | 2021-08-04 12:40:00 | 2021-10-23 16:40:00 | heli | 6926400.0 | 80.166667 |
| heli_2022-03-23 18:40:00 | POINT Z (77.44329 18.45058 -2565.76000) | 2022-03-23 18:40:00 | 2022-04-03 01:20:00 | heli | 888000.0 | 10.277778 |
stop_point_plot = stop_points.hvplot(title='M20 & heli stops ',
geo=True, size=np.log(dim('duration_days'))*10,
hover_cols=['duration_days'], color='blue', alpha=0.5)
heli_stop_plot = heli_stop_points.hvplot(geo=True, size=np.log(dim('duration_days'))*10,
hover_cols=['duration_days'], color='red', alpha=0.5)
stop_point_plot * heli_stop_plot * traj_plot
Status: still looking for suitable background maps, since OpenPlanetary maps don't go into enough detail, e.g.
from geoviews.element import WMTS
layer = 'celestia_mars-shaded-16k_global'
MarsImagery = WMTS(
'http://s3-eu-west-1.amazonaws.com/whereonmars.cartodb.net/'+layer+'/{Z}/{X}/{Y}.png',
name="Mars")
MarsImagery
m20_traj.hvplot(title="M20 & heli trajectories", tiles=MarsImagery) * heli_traj.hvplot(**hvplot_defaults)